package com.mtoolkit.jdbc.support;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import com.mtoolkit.jdbc.JdbcTemplate;
import com.mtoolkit.jdbc.PersistenceException;
import com.mtoolkit.jdbc.handler.LongResultSetHandler;
import com.mtoolkit.jdbc.handler.ResultSetHandler;
import com.mtoolkit.page.Page;
import com.mtoolkit.proxy.Interceptor;
import com.mtoolkit.util.ArrayUtil;
import com.mtoolkit.util.LogUtil;


/**
 * Abstract JDBC template implementation that provides 
 * common database operations implement use jdbc-api. 
 * 
 * @author 	<a href="mailto:xishizhang@gmail.com">ZhangShixi</a>
 * @version 1.0, 12/7/2011
 * @since 	JDK1.5
 */
public abstract class AbstractJdbcTemplate implements JdbcTemplate {
	
	/** initialize flag */
	private volatile boolean _initialized;
	/** asynchronous update executor service */
	private ExecutorService _asyncExecutorService;
	/** transaction intercepter */
	private Interceptor 	_transactionInterceptor;	
	/** asynchronous thread pool size */
	private int 			_asyncThreadPoolSize;
	
	/** unlimited size of page query */
	public static final int UNLIMITED_SIZE = -1;
	
	/** logger */
	protected static final LogUtil LOGGER = LogUtil.getLogger(JdbcTemplate.class);
	
	/**
	 * Returns transaction intercepter.
	 * 
	 * @return transaction intercepter.
	 */
	public Interceptor getTransactionInterceptor() {
		return _transactionInterceptor;
	}
	
	/**
	 * Sets database transaction intercepter.
	 * 
	 * @param transactionInterceptor transaction intercepter.
	 */
	public void setTransactionInterceptor(Interceptor transactionInterceptor) {
		_transactionInterceptor = transactionInterceptor;
	}
	
	/**
	 * Returns asynchronous update thread pool size.
	 * 
	 * @return asynchronous update thread pool size.
	 */
	public int getAsyncThreadPoolSize() {
		return _asyncThreadPoolSize;
	}
	
	/**
	 * Sets asynchronous update thread pool size.
	 * 
	 * @param asyncThreadPoolSize asynchronous update thread pool size.
	 */
	public void setAsyncThreadPoolSize(int asyncThreadPoolSize) {
		_asyncThreadPoolSize = asyncThreadPoolSize;
	}
	
	/**
	 * Initializes current JDBC template.
	 */
	public void init() {
		if (_initialized) {
			return;
		}
		if (_transactionInterceptor == null) {
			return;
		}
		
		if (_asyncThreadPoolSize <= 0) {
			_asyncExecutorService = Executors.newSingleThreadExecutor();
		} else {
			_asyncExecutorService = Executors.newFixedThreadPool(_asyncThreadPoolSize);
		}
		
		LOGGER.debug("Create thread pool with size [{0}] for async update.",
                _asyncThreadPoolSize <= 0 ? 1 : _asyncThreadPoolSize);
		
		_initialized = true;
		
		LOGGER.info("JdbcTemplate was initialized.");
	}
	
	/**
	 * Destroy current JDBC template.
	 */
	public void destroy() {
		if (!_initialized) {
			return;
		}
		if (_asyncExecutorService == null) {
			return;
		}
		
		_asyncExecutorService.shutdown();
		
		try {
			if (!_asyncExecutorService.awaitTermination(5L, TimeUnit.SECONDS)) {
				_asyncExecutorService.shutdownNow();
			}
		} catch (InterruptedException e) {
			_asyncExecutorService.shutdownNow();
			Thread.currentThread().interrupt();
		}
		
		LOGGER.info("JdbcTemplate was destroyed.");
	}

	// ---- insert ----------------------------------------------------------------
	/**
     * {@inheritDoc}	
     *
     * @param  conn		 {@inheritDoc}
     * @param  keySql	 {@inheritDoc}
     * @param  insertSql {@inheritDoc}
     * 
     * @return {@inheritDoc}
     * 
     * @throws NullPointerException {@inheritDoc}
     * @throws PersistenceException {@inheritDoc}	
     * 
     * @see	   #executeKeyInsert(Connection, String, String, Object...)
     */
	@Override
	public Number executeKeyInsert(Connection conn, 
								   String keySql,
								   String insertSql) {
		return executeKeyInsert(conn, keySql, insertSql, (Object[]) null);
	}
	
	/**
     * {@inheritDoc}	
     *
     * @param  conn 	 {@inheritDoc}
     * @param  keySql	 {@inheritDoc}
     * @param  insertSql {@inheritDoc}
     * @param  params 	 {@inheritDoc}
     * 
     * @return {@inheritDoc}
     * 
     * @throws NullPointerException 	{@inheritDoc}
     * @throws IllegalArgumentException {@inheritDoc}
     * @throws PersistenceException 	{@inheritDoc}	
     * 
     * @see	   #executeKeyInsert(Connection, String, String, Object[][])
     */
	@Override
	public Number executeKeyInsert(Connection conn, 
								   String keySql,
								   String insertSql,
								   Object... params) {
		Number[] keys = executeKeyInsert(conn, keySql, insertSql, new Object[][]{params});
		return keys[0];
	}

	/**
     * {@inheritDoc}	
     *
     * @param  conn 	 {@inheritDoc}
     * @param  keySql	 {@inheritDoc}
     * @param  insertSql {@inheritDoc}
     * @param  params 	 {@inheritDoc}
     * 
     * @return {@inheritDoc}
     * 
     * @throws NullPointerException 	{@inheritDoc}
     * @throws IllegalArgumentException {@inheritDoc}
     * @throws PersistenceException		{@inheritDoc}	
     * 
     * @see	   #executeKeyInsert(Connection, String, String, Object[][], int)
     */
	@Override
	public Number[] executeKeyInsert(Connection conn, 
									 String keySql,
									 String insertSql,
									 Object[][] params) {
		int batchSize = (params == null ? 0 : params.length);
		return executeKeyInsert(conn, keySql, insertSql, params, batchSize);
	}
	
	// ---- insert/update/delete --------------------------------------------------
	/**
     * {@inheritDoc}
     * 
     * @param  conn {@inheritDoc}
     * @param  sql  {@inheritDoc}
     * 
     * @return {@inheritDoc}
     * 
     * @throws NullPointerException {@inheritDoc}
     * @throws PersistenceException	{@inheritDoc}	
     * 
     * @see	   #executeUpdate(Connection, String, Object...)
     */
	@Override
	public int executeUpdate(Connection conn, 
							 String sql) {
		return executeUpdate(conn, sql, (Object[]) null);
	}

	/**
     * {@inheritDoc}
     * 
     * @param  conn   {@inheritDoc}
     * @param  sql    {@inheritDoc}
     * @param  params {@inheritDoc}
     * 
     * @return {@inheritDoc}
     * 
     * @throws NullPointerException 	{@inheritDoc}
     * @throws IllegalArgumentException {@inheritDoc}
     * @throws PersistenceException		{@inheritDoc}	
     * 
     * @see	   #executeBatchUpdate(Connection, String, Object[][])
     */
	@Override
	public int executeUpdate(Connection conn, 
							 String sql, 
							 Object... params) {
		int[] rows = executeBatchUpdate(conn, sql, new Object[][]{params});
		return rows[0];
	}

	/**
     * {@inheritDoc}
     * 
     * @param  conn   {@inheritDoc}
     * @param  sql    {@inheritDoc}
     * @param  params {@inheritDoc}
     * 
     * @return {@inheritDoc}
     * 
     * @throws NullPointerException 	{@inheritDoc}
     * @throws IllegalArgumentException {@inheritDoc}
     * @throws PersistenceException		{@inheritDoc}	
     * 
     * @see	   #executeBatchUpdate(Connection, String, Object[][], int)
     */
	@Override
	public int[] executeBatchUpdate(Connection conn, 
									String sql,
									Object[][] params) {
		int batchSize = (params == null ? 0 : params.length);
		return executeBatchUpdate(conn, sql, params, batchSize);
	}
	
	/**
     * {@inheritDoc}
     * 
     * @param  conn		 {@inheritDoc}
     * @param  sql		 {@inheritDoc}
     * @param  params 	 {@inheritDoc}
     * @param  batchSize {@inheritDoc}
     * 
     * @return {@inheritDoc}
     * 
     * @throws NullPointerException 	{@inheritDoc}
     * @throws IllegalArgumentException {@inheritDoc}
     * @throws PersistenceException		{@inheritDoc}	
     */
	@Override
	public int[] executeBatchUpdate(Connection conn, 
									String sql,
									Object[][] params, 
									int batchSize) {
		checkConnection(conn);
		checkSql(sql);
		
		PreparedStatement stmt = null;
		int[] rowCounts = null;
		
		try {
			stmt = prepareStatement(conn, sql, Statement.NO_GENERATED_KEYS);
			
			if (params == null) {
				int count = stmt.executeUpdate();
				rowCounts = new int[]{count};
				
			} else {
				rowCounts = new int[params.length];
				int newBatchSize = batchSize < params.length ? batchSize : params.length;
				int index = 0;
				int remaining = 0;
				int[] batchCounts = null;
				
				for (int i = 0; i < params.length; i++) {
					fillStatement(stmt, params[i]);
					stmt.addBatch();
					
					remaining = (i + 1) % newBatchSize;
					if (remaining == 0) {
						batchCounts = stmt.executeBatch();
						
						for (int j = 0; j < batchCounts.length; j++) {
							rowCounts[index++] = batchCounts[j];
						}
					}
				}
				
				// execute remaining statements.
				if (remaining != 0) {
					batchCounts = stmt.executeBatch();
					
					for (int j = 0; j < batchCounts.length; j++) {
						rowCounts[index++] = batchCounts[j];
					}
				}
			}

			return rowCounts;
		} catch(SQLException e) {
			throw new PersistenceException(e.getMessage(), e);
		} finally {
			try {
				close(stmt);
			} catch(SQLException e) {
				throw new PersistenceException(e.getMessage(), e);
			}
		}
	}

	// ---- delay insert/update/delete --------------------------------------------
	/**
     * {@inheritDoc}
     * 
     * @param  conn {@inheritDoc}
     * @param  sql  {@inheritDoc}
     * 
     * @return {@inheritDoc}
     * 
     * @throws NullPointerException {@inheritDoc}
     * @throws PersistenceException	{@inheritDoc}	
     * 
     * @see	   #executeDelayUpdate(Connection, String, Object...)
     */
	@Override
	public Future<Integer> executeDelayUpdate(Connection conn, 
											  String sql) {
		return executeDelayUpdate(conn, sql, (Object[]) null);
	}

	/**
     * {@inheritDoc}
     * 
     * @param  conn   {@inheritDoc}
     * @param  sql    {@inheritDoc}
     * @param  params {@inheritDoc}
     * 
     * @return {@inheritDoc}
     * 
     * @throws NullPointerException 	{@inheritDoc}
     * @throws IllegalArgumentException {@inheritDoc}
     * @throws PersistenceException		{@inheritDoc}	
     */
	@Override
	public Future<Integer> executeDelayUpdate(Connection conn, 
											  String sql,
											  Object... params) {
		Future<Integer> row;
		if (_transactionInterceptor == null) {
			row = new SuccessFuture(executeUpdate(conn, sql, params));
		} else {
			row = _asyncExecutorService.submit(new AsyncUpdateTask(conn, sql, params));
		}
		
		return row;
	}

	/**
     * {@inheritDoc}
     * 
     * @param  conn   {@inheritDoc}
     * @param  sql    {@inheritDoc}
     * @param  params {@inheritDoc}
     * 
     * @return {@inheritDoc}
     * 
     * @throws NullPointerException 	{@inheritDoc}
     * @throws IllegalArgumentException {@inheritDoc}
     * @throws PersistenceException		{@inheritDoc}	
     * 
     * @see    #executeBatchDelayUpdate(Connection, String, Object[][], int)
     */
	@Override
	public Future<Integer[]> executeBatchDelayUpdate(Connection conn,
													 String sql, 
													 Object[][] params) {
		int batchSize = (params == null ? 0 : params.length);
		return executeBatchDelayUpdate(conn, sql, params, batchSize);
	}

	/**
     * {@inheritDoc}
     * 
     * @param  conn   	 {@inheritDoc}
     * @param  sql    	 {@inheritDoc}
     * @param  params 	 {@inheritDoc}
     * @param  batchSize {@inheritDoc}
     * 
     * @return {@inheritDoc}
     * 
     * @throws NullPointerException 	{@inheritDoc}
     * @throws IllegalArgumentException {@inheritDoc}
     * @throws PersistenceException		{@inheritDoc}	
     */
	@Override
	public Future<Integer[]> executeBatchDelayUpdate(Connection conn,
													 String sql, 
													 Object[][] params, 
													 int batchSize) {
		Future<Integer[]> rows;
		if (_transactionInterceptor == null) {
			rows = new BatchSuccessedFuture(executeBatchUpdate(conn, sql, params, batchSize));
		} else {
			rows = _asyncExecutorService.submit(new AsyncBatchUpdateTask(conn, sql, params, batchSize));
		}
		
		return rows;
	}

	// ---- query -----------------------------------------------------------------
	/**
     * {@inheritDoc}
     * 
     * @param  <T> 	   {@inheritDoc}
     * @param  conn	   {@inheritDoc}
     * @param  sql 	   {@inheritDoc}
     * @param  handler {@inheritDoc}
     * 
     * @return {@inheritDoc}
     * 
     * @throws NullPointerException {@inheritDoc}
     * @throws PersistenceException	{@inheritDoc} 
     * 
     * @see	   #executeQuery(Connection, String, ResultSetHandler, Object...)
     */
	@Override
	public <T> T executeQuery(Connection conn, 
							  String sql,
							  ResultSetHandler<T> handler) {
		return executeQuery(conn, sql, handler, (Object[]) null);
	}

	/**
     * {@inheritDoc}
     * 
     * @param  <T> 	   {@inheritDoc}
     * @param  conn	   {@inheritDoc}
     * @param  sql 	   {@inheritDoc}
     * @param  handler {@inheritDoc}
     * @param  params  {@inheritDoc}
     * 
     * @return {@inheritDoc}
     * 
     * @throws NullPointerException 	{@inheritDoc}
     * @throws IllegalArgumentException {@inheritDoc}
     * @throws PersistenceException		{@inheritDoc} 
     */
	@Override
	public <T> T executeQuery(Connection conn, 
							  String sql,
							  ResultSetHandler<T> handler, 
							  Object... params) {
		checkConnection(conn);
		checkSql(sql);
		checkHandler(handler);
		
		PreparedStatement stmt = null;
		ResultSet rs = null;
		try {
			stmt = prepareStatement(conn, sql, Statement.NO_GENERATED_KEYS);
			
			fillStatement(stmt, params);
			rs = stmt.executeQuery();
		
			return handler.handle(rs);
		} catch(SQLException e) {
			throw new PersistenceException(e.getMessage(), e);
		} finally {
			try {
				close(rs, stmt);
			} catch(SQLException e) {
				throw new PersistenceException(e.getMessage(), e);
			}
		}
	}

	/**
     * {@inheritDoc}
     * 
     * @param  conn	{@inheritDoc}
     * @param  sql 	{@inheritDoc}
     * 
     * @return {@inheritDoc}
     * 
     * @throws NullPointerException {@inheritDoc}
     * @throws PersistenceException	{@inheritDoc} 
     * 
     * @see	   #executeCountQuery(Connection, String, Object...)
     */
	@Override
	public long executeCountQuery(Connection conn, 
								  String sql) {
		return executeCountQuery(conn, sql, (Object[]) null);
	}

	/**
     * {@inheritDoc}
     * 
     * @param  conn	  {@inheritDoc}
     * @param  sql 	  {@inheritDoc}
     * @param  params {@inheritDoc}
     * 
     * @return {@inheritDoc}
     * 
     * @throws NullPointerException 	{@inheritDoc}
     * @throws IllegalArgumentException {@inheritDoc}
     * @throws PersistenceException		{@inheritDoc} 
     * 
     * @see	   #executeQuery(Connection, String, ResultSetHandler, Object...)
     * @see	   com.mtoolkit.jdbc.handler.LongResultSetHandler
     */
	@Override
	public long executeCountQuery(Connection conn, 
								  String sql, 
								  Object... params) {
		return executeQuery(conn, sql, new LongResultSetHandler(), params).longValue();
	}

	/**
     * {@inheritDoc}
     * 
     * @param  <T>	    {@inheritDoc}
     * @param  conn	    {@inheritDoc}
     * @param  countSql {@inheritDoc}
     * @param  querySql {@inheritDoc}
     * @param  page	    {@inheritDoc}
     * @param  handler  {@inheritDoc}
     * 
     * @return {@inheritDoc}
     * 
     * @throws NullPointerException {@inheritDoc}
     * @throws PersistenceException	{@inheritDoc}
     * 
     * @see	   #executePageQuery(Connection, String, String, Page, ResultSetHandler, Object...)
     */
	@Override
	public <T> T executePageQuery(Connection conn, 
								  String countSql,
								  String querySql, 
								  Page<?> page, 
								  ResultSetHandler<T> handler) {
		return executePageQuery(conn, countSql, querySql, page, handler, (Object) null);
	}

	/**
     * {@inheritDoc}
     * 
     * @param  <T>	    {@inheritDoc}
     * @param  conn	    {@inheritDoc}
     * @param  countSql {@inheritDoc}
     * @param  querySql {@inheritDoc}
     * @param  page	    {@inheritDoc}
     * @param  handler  {@inheritDoc}
     * @param  params   {@inheritDoc}
     * 
     * @return {@inheritDoc}
     * 
     * @throws NullPointerException 	{@inheritDoc}
     * @throws IllegalArgumentException {@inheritDoc}
     * @throws PersistenceException		{@inheritDoc}
     * 
     * @see	   #executePageQuery(Connection, String, String, Page, ResultSetHandler, int, Object...)
     */
	@Override
	public <T> T executePageQuery(Connection conn, 
								  String countSql,
								  String querySql, 
								  Page<?> page, 
								  ResultSetHandler<T> handler,
								  Object... params) {
		return executePageQuery(conn, countSql, querySql, page, handler, UNLIMITED_SIZE, params);
	}

	/**
     * {@inheritDoc}
     * 
     * @param  <T>	     {@inheritDoc}
     * @param  conn	     {@inheritDoc}
     * @param  countSql  {@inheritDoc}
     * @param  querySql  {@inheritDoc}
     * @param  page	     {@inheritDoc}
     * @param  handler   {@inheritDoc}
     * @param  limitSize {@inheritDoc}
     * @param  params    {@inheritDoc}
     * 
     * @return {@inheritDoc}
     * 
     * @throws NullPointerException 	{@inheritDoc}
     * @throws IllegalArgumentException {@inheritDoc}
     * @throws PersistenceException		{@inheritDoc}
     */
	@Override
	public <T> T executePageQuery(Connection conn, 
								  String countSql,
								  String querySql, 
								  Page<?> page, 
								  ResultSetHandler<T> handler,
								  int limitSize, 
								  Object... params) {
		checkConnection(conn);
		checkSql(countSql);
		checkSql(querySql);
		checkHandler(handler);
		
		Page<?> newPage = null;
		String executeSql = null;
		
		if (page == null) { // query all data.
			
			if (limitSize <= 0) { // unlimited.
				executeSql = querySql;
			} else { // limited.
				newPage = Page.newFirstPage(limitSize);
				executeSql = doFillExecuteSql(querySql, newPage);
			}
			
		} else { // query page data.
			
			long dataCounts = executeCountQuery(conn, countSql, params);
			
			if (limitSize > 0 && dataCounts > limitSize) {
				dataCounts = limitSize;
			}
			
			// set total data.
			try {
				newPage = (Page<?>) page.clone();
			} catch (CloneNotSupportedException e) {
				throw new PersistenceException(e.getMessage(), e);
			}
			newPage.setTotalData((int) dataCounts);
			
			executeSql = doFillExecuteSql(querySql, newPage);
		}
		
		return executeQuery(conn, executeSql, handler, params);
	}
	
	/**
	 * Builds actually execute SQL statement with specified query page info.
	 * 
	 * @param  sql  SQL statement template.
	 * @param  page query page info.
	 * 
	 * @return actually execute SQL statement witch fills query page info.
	 */
	protected abstract String doFillExecuteSql(String sql, Page<?> page);
	
	
	// ---- protected methods ----------------------------------------------------
	protected void checkConnection(Connection conn) {
		if (conn == null) {
			throw new NullPointerException("connection");
		}
	}
	
	protected void checkSql(String sql) {
		if (sql == null) {
			throw new NullPointerException("sql");
		}
	}
	
	protected void checkGeneratedKeysSupport(Connection conn) throws SQLException {
		boolean support = conn.getMetaData().supportsGetGeneratedKeys();
		if (!support) {
			throw new UnsupportedOperationException(
				  "Generate key supported from JDBC 3.0.");
		}
	}
	
	protected void checkHandler(ResultSetHandler<?> handler) {
		if (handler == null) {
			throw new NullPointerException("handler");
		}
	}

	protected PreparedStatement prepareStatement(Connection conn, 
											   String sql, 
											   int autoGeneratedKeys) 
											   throws SQLException {
		LOGGER.debug("Execute Sql: {0}", sql);
		return conn.prepareStatement(sql, autoGeneratedKeys);
	}

	
	protected void fillStatement(PreparedStatement stmt, 
							   Object[] params) 
							   throws SQLException {
		LOGGER.debug("Sql parameter: {0}", Arrays.toString(params));
		
		if (params != null) {
			int paramCount = stmt.getParameterMetaData().getParameterCount();
			if (paramCount != params.length) {
				throw new IllegalArgumentException(
					  "parameter number not match '?' in SQL statement");
			}
			
			for (int i = 0; i < params.length; i++) {
				stmt.setObject(i + 1, params[i]);
			}
		}
	}
	
	protected void close(ResultSet rs) throws SQLException {
		close(rs, null);
	}
	
	protected void close(PreparedStatement stmt) throws SQLException {
		close(null, stmt);
	}
	
	protected void close(ResultSet rs, PreparedStatement stmt) throws SQLException {
		if (rs 	 != null) { rs.close();   }
		if (stmt != null) { stmt.close(); }
	}
	
	// ---- inner classes --------------------------------------------------------
	private class AsyncUpdateTask implements Callable<Integer> {
		
		private Connection _conn;
		private String 	   _sql;
		private Object[]   _params;
		
		public AsyncUpdateTask(Connection conn, String sql, Object[] params) {
			_conn 	= conn;
			_sql	= sql;
			_params = params;
		}
		
		@Override
		public Integer call() throws Exception {
			return Integer.valueOf(executeUpdate(_conn, _sql, _params));
		}
		
	}
	
	private class AsyncBatchUpdateTask implements Callable<Integer[]> {
		
		private Connection _conn;
		private String 	   _sql;
		private Object[][] _params;
		private int 	   _batchSize;
		
		public AsyncBatchUpdateTask(Connection conn, String sql, Object[][] params, int batchSize) {
			_conn 	   = conn;
			_sql 	   = sql;
			_params    = params;
			_batchSize = batchSize;
		}
		
		@Override
		public Integer[] call() throws Exception {
			return ArrayUtil.pack(executeBatchUpdate(_conn, _sql, _params, _batchSize));
		}
		
	}
	
	private abstract class AbstractSuccessedFuture<T> implements Future<T> {

		@Override
		public boolean cancel(boolean mayInterruptIfRunning) {
			throw new UnsupportedOperationException();
		}

		@Override
		public T get(long timeout, TimeUnit unit) 
			   throws InterruptedException, ExecutionException, TimeoutException {
			return get();
		}

		@Override
		public boolean isCancelled() {
			throw new UnsupportedOperationException();
		}

		@Override
		public boolean isDone() {
			throw new UnsupportedOperationException();
		}
		
	}
	
	private class SuccessFuture extends AbstractSuccessedFuture<Integer> {
		
		private int _row;
		
		public SuccessFuture(int row) {
			_row = row;
		}

		@Override
		public Integer get() throws InterruptedException, ExecutionException {
			return Integer.valueOf(_row);
		}

	}
	
	private class BatchSuccessedFuture extends AbstractSuccessedFuture<Integer[]> {

		private int[] _rows;
		
		public BatchSuccessedFuture(int[] rows) {
			_rows = rows;
		}

		@Override
		public Integer[] get() throws InterruptedException, ExecutionException {
			return ArrayUtil.pack(_rows);
		}

	}
	
}

